package citic.c.util;

import java.util.*;

/**
 * 组合单位,比如单价单位(元/小时;元 /分钟)
 */
public class CombinUnit {

    private Fraction k = new Fraction(1, 1);

    private Map<UnitType, Unit> base = new TreeMap<>();

    private Map<Unit, Integer> ups = new TreeMap<>();

    private Map<Unit, Integer> downs = new TreeMap<>();

    public CombinUnit(Unit up, Unit down) {
        this(null, up == null ? null : Arrays.asList(up), down == null ? null : Arrays.asList(down));
    }

    public CombinUnit(Fraction k, Unit up, Unit down) {
        this(k, up == null ? null : Arrays.asList(up), down == null ? null : Arrays.asList(down));
    }

    public CombinUnit(Fraction k, List<Unit> ups, List<Unit> downs) {
        if (k != null) {
            this.k = k;
        }
        if (ups != null) {
            for (Unit unit : ups) {
                Unit base = this.base.get(unit.getUnitType());
                if (base == null) {
                    this.base.put(unit.getUnitType(), unit);
                } else {
                    this.base.put(unit.getUnitType(), unit);
                    this.k = this.k.multiply(new Fraction(base.getRatio().longValue(), unit.getRatio().longValue()));
                }
            }
        }

        if (downs != null) {
            for (Unit unit : downs) {
                Unit base = this.base.get(unit.getUnitType());
                if (base == null) {
                    this.base.put(unit.getUnitType(), unit);
                } else {
                    this.base.put(unit.getUnitType(), unit);
                    this.k = this.k.devide(new Fraction(base.getRatio().longValue(), unit.getRatio().longValue()));
                }
            }
        }

        if (ups != null) {
            for (Unit unit : ups) {
                Integer num = this.ups.get(this.base.get(unit.getUnitType()));
                if (num == null) {
                    this.ups.put(this.base.get(unit.getUnitType()), 1);
                } else {
                    this.ups.put(this.base.get(unit.getUnitType()), num + 1);
                }
            }
        }

        if (downs != null) {
            for (Unit unit : downs) {
                Integer num = this.downs.get(this.base.get(unit.getUnitType()));
                if (num == null) {
                    this.downs.put(this.base.get(unit.getUnitType()), 1);
                } else {
                    this.downs.put(this.base.get(unit.getUnitType()), num + 1);
                }
            }
        }

        List<Unit> removedUps = new ArrayList<>();
        List<Unit> removedDowns = new ArrayList<>();

        for (Unit unit : this.ups.keySet()) {
            if (this.downs.keySet().contains(unit)) {
                Integer n = this.ups.get(unit) - this.downs.get(unit);
                if (n > 0) {
                    this.ups.put(unit, n);
                    removedDowns.add(unit);
                } else if (n < 0) {
                    this.downs.put(unit, n);
                    removedUps.add(unit);
                } else {
                    removedDowns.add(unit);
                    removedUps.add(unit);
                }
            }
        }

        for (Unit unit : removedUps) {
            this.ups.remove(unit);
        }

        for (Unit unit : removedDowns) {
            this.downs.remove(unit);
        }

    }

    private Map<UnitType, Unit> createBase(List<Unit> ups, List<Unit> downs) {
        Map<UnitType, Unit> nbase = new HashMap<>();
        if (ups != null) {
            for (Unit unit : ups) {
                Unit base = nbase.get(unit.getUnitType());
                if (base == null) {
                    nbase.put(unit.getUnitType(), unit);
                } else {
                    nbase.put(unit.getUnitType(), unit);
                    k = k.multiply(new Fraction(base.getRatio().longValue(), unit.getRatio().longValue()));
                }
            }
        }

        if (downs != null) {
            for (Unit unit : downs) {
                Unit base = nbase.get(unit.getUnitType());
                if (base == null) {
                    nbase.put(unit.getUnitType(), unit);
                } else {
                    nbase.put(unit.getUnitType(), unit);
                    k = k.devide(new Fraction(base.getRatio().longValue(), unit.getRatio().longValue()));
                }
            }
        }
        return nbase;
    }

    private List<Unit> getUpUnits(CombinUnit o1, CombinUnit o2) {
        List<Unit> newUps = new ArrayList<>();
        for (Unit u : o1.ups.keySet()) {
            for (int i = 0; i < o1.ups.get(u); i++) {
                newUps.add(u);
            }
        }
        for (Unit u : o2.ups.keySet()) {
            for (int i = 0; i < o2.ups.get(u); i++) {
                newUps.add(u);
            }
        }
        return newUps;
    }

    private List<Unit> getDownUnits(CombinUnit o1, CombinUnit o2) {
        List<Unit> newDowns = new ArrayList<>();
        for (Unit u : o1.downs.keySet()) {
            for (int i = 0; i < o1.downs.get(u); i++) {
                newDowns.add(u);
            }
        }
        for (Unit u : o2.downs.keySet()) {
            for (int i = 0; i < o2.downs.get(u); i++) {
                newDowns.add(u);
            }
        }
        return newDowns;
    }

    /**
     * 是否是一个基本单位
     * @return
     */
    public Boolean isBaseUnit() {
        if (this.downs.size() == 0 && this.ups.size() == 1) {
            if (this.ups.values().toArray()[0].equals(1)) {
                return true;
            } else {
                return false;
            }

        } else {
            return false;
        }
    }

    /**
     * 返回基本单位,不是基本单位返回空
     * @return
     */
    public Unit toBaseUnit() {
        if (isBaseUnit()) {
            return (Unit) this.ups.keySet().toArray()[0];
        } else {
            return null;
        }
    }

    /**
     * 变化到目标单位
     * @param combinUnit
     * @return
     */
    public CombinUnit toUnit(CombinUnit combinUnit) {
        if(!isSameType(combinUnit)){
            return this;
        }
        Fraction newk=combinUnit.getK();
        for(Unit unit:combinUnit.getUps().keySet()){
            Unit oldUnit=this.getBase().get(unit.getUnitType());
            for(int i=0;i<combinUnit.getUps().get(unit);i++){
                newk=newk.multiply(new Fraction(unit.getRatio().longValue(),oldUnit.getRatio().longValue()));
            }
        }

        for(Unit unit:combinUnit.getDowns().keySet()){
            Unit oldUnit=this.getBase().get(unit.getUnitType());
            for(int i=0;i<combinUnit.getDowns().get(unit);i++){
                newk=newk.devide(new Fraction(unit.getRatio().longValue(),oldUnit.getRatio().longValue()));
            }
        }
        newk=newk.multiply(this.getK());
        combinUnit.setK(newk);
        return combinUnit;
    }

    /**
     * 是否是可以加减的单位
     * @return
     */
    public Boolean isSimpleUnit() {
        if (this.downs.size() == 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 判断是否和目标单位一致
     * @param combinUnit
     * @return
     */
    public Boolean isSameType(CombinUnit combinUnit){
        if(this.getBase().size()!=combinUnit.getBase().size()){
            return false;
        }
        for(UnitType unitType:this.getBase().keySet()){
            if(!combinUnit.getBase().containsKey(unitType)){
                return false;
            }
        }
        for(UnitType unitType:this.getBase().keySet()){
            Integer numU=this.ups.get(this.base.get(unitType));
            if(numU!=null){
                if(numU!=combinUnit.getUps().get(combinUnit.getBase().get(unitType))){
                    return false;
                }
            }
            Integer numD=this.downs.get(this.base.get(unitType));
            if(numD!=null){
                if(numD!=combinUnit.getDowns().get(combinUnit.getBase().get(unitType))){
                    return false;
                }
            }
        }
        return true;
    }


    @Override
    public String toString() {
        return "CombinUnit{" +
                "k=" + k +
                ", ups=" + ups +
                ", downs=" + downs +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CombinUnit that = (CombinUnit) o;
        return Objects.equals(k, that.k) &&
                Objects.equals(ups, that.ups) &&
                Objects.equals(downs, that.downs);
    }

    @Override
    public int hashCode() {
        return Objects.hash(k, ups, downs);
    }

    public Fraction getK() {
        return k;
    }

    public void setK(Fraction k) {
        this.k = k;
    }

    public Map<UnitType, Unit> getBase() {
        return base;
    }

    public Map<Unit, Integer> getUps() {
        return ups;
    }

    public Map<Unit, Integer> getDowns() {
        return downs;
    }

    public static void main(String[] args) {
        List<Unit> ups = new ArrayList<>();
        ups.add(Unit.TIME_MINUTE);
        ups.add(Unit.TIME_MINUTE);
        ups.add(Unit.TIME_HOUR);
        ups.add(Unit.CNY_HAO);
        List<Unit> downs = new ArrayList<>();
        downs.add(Unit.TIME_MINUTE);
        downs.add(Unit.CNY_HAO);
        downs.add(Unit.STORAGE_b);
        CombinUnit combinUnit1 = new CombinUnit(null, ups, downs);
        System.out.println(combinUnit1.toString());
        Boolean b1=combinUnit1.isSameType(combinUnit1);
        System.out.println(b1);
        CombinUnit combinUnit2 = combinUnit1.toUnit(combinUnit1);
        System.out.println(combinUnit2.toString());
    }


}
